#ifndef _ELEMENT_HH_
#define _ELEMENT_HH_

#include <stdlib.h>
#include <time.h>

#include <vector>
#include "boost/smart_ptr.hpp"

#include "cxcore.h"

#include "basic_utils.hh"
#include "shape.hh"
#include "AppearanceBase.hh"

const unsigned int ELEMENT_MAX_PATCHES=20;
const unsigned int ELEMENT_MAX_CLUSTERS=10;

const unsigned int PATCHCLUSTER_MAX = ELEMENT_MAX_PATCHES/2;

const float LEARNING_RATE = 0.01;

const unsigned int GRID_SIZE_MAX = 15;
const unsigned int GRID_SIZE_MIN = 4;
const unsigned int GRID_STEP = 6;

const double MAX_PROJ_NCC_ERROR = 0.05;

const double MIN_CLUSTER_DISTANCE = 0.5;

class PatchCluster{

public:

    PatchCluster(unsigned int max_size = PATCHCLUSTER_MAX);

    std::vector<boost::shared_ptr<IplImage> > exemplars;
    unsigned int m_rep; //median (representative). Index to the exemplars vector.
    CvMat *m_distances;
    CvMat *m_pos_matches;
    CvMat *m_neg_matches;

    //index to the last element of the m_neg_matches that has been
    //initialized
    unsigned int m_negm;

    
    //the total number of patches that has been included in this
    //cluster, including those removed when the max size was
    //reached. This is used as a long-term weight of the cluster.
    int m_total_patches;

//     unsigned int modify_size(unsigned int new_max_size);

    /** 
     * Add a patch to the cluster.Recalculates the distance matrix and
     * the representative of the cluster.
     * 
     * @param patches All the patches of the element
     * @param index The index of the new patch in \a patches
     * @param pos_match The value of the positive match with the representative of the cluster
     * @param neg_match The value of the negative match with the representative
     * 
     * @return The number of patches in the cluster, or -1 is an error occurs
     */
    unsigned int add_patch(std::vector<boost::shared_ptr<IplImage> >& patches,
		  int index, double pos_match, std::vector<double> neg_match);

    void calculate_representative();

    ///Remove the most distant patch
    void purge();

    void get_pos_match_stats(double *pos_mean, double *pos_stv);

    void clear(){
	cvReleaseMat(&m_distances);
	cvReleaseMat(&m_pos_matches);
	cvReleaseMat(&m_neg_matches);
    }

    ~PatchCluster(){clear();}

private:

    unsigned int m_max_size;

};


class Element{

public:

    Element(CvSize size);

    int load_app_base(const char *file);

    CvSize m_size;
    std::vector<boost::shared_ptr<IplImage> > patches;

    std::vector<boost::shared_ptr<PatchCluster> > clusters;
    
    CvPoint position;
    std::vector<float> cluster_weight;
    
    unsigned int m_max_patches;
    unsigned int m_max_clusters;

    void get_pos_match_stats(double *pos_mean, double *pos_stv = NULL);
    void get_neg_match_stats(double *neg_mean, double *neg_stv = NULL);
    void add_matching_values(double positive, std::vector<double> negatives);
    
    void purge();

    double get_projection_error(boost::shared_ptr<IplImage> image, 
				  bool use_ncc = false);

    CvPoint fit_element(boost::shared_ptr<IplImage> image, CvPoint seed);
    unsigned int m_grid_size, m_grid_step;

    unsigned int m_updated_cluster;

    bool remove_last_patch();

//     ~Element();

private:

    boost::shared_ptr<CvMat> m_pos_matches, m_neg_matches;
    unsigned int m_posm, m_negm;   

    
    AppearanceBase app_base;
    void erase_cluster(unsigned int idx);
};



class SMATModel{

public:
    SMATModel(int depth = -1){if(depth<0) m_depth = -1; else m_depth = depth;}

    /** 
     * 
     * 
     * @param img A boost::shared_ptr to an IplImage @param seed The
     * reference point of the model in the image @param mask A
     * optional mask, with size (# of elements, 1). All zero
     * components will not be tracked in the frame. For multi-level
     * systems, this mask only takes effect on the first level.
     * 
     * @return 
     */
    int track(boost::shared_ptr<IplImage> img, CvPoint seed, const CvMat *mask = NULL);
    
    /** 
    * remove the last patch added to the elements set as !0 in the
    mask.  
    * 
    * @param element_mask A Nx1 matrix
    * 
    * @return the number of elements whose last patch has been
    * successfully removed.
    */
    unsigned int remove_last_added(const CvMat *element_mask);

    
    /** 
     * Remove an element from the model. This function is only
     * available is the Shape of the model does not use a pre-learnt
     * model.
     * 
     * @param index The index of the element to be removed
     * 
     * @return True of false, if the element was correctly removed or
     * not.
     */
    bool remove_element(unsigned int index);
    
    void set_robust_shape(bool robust);

    std::vector<boost::shared_ptr<Element> > elements;

    boost::shared_ptr<Shape> shape;

    int m_depth;

    std::vector<boost::shared_ptr<SMATModel> > submodels;
    //the index of the element that is the parent of the submodel.
    std::vector<unsigned int> submodel_parents;

private:   

};


boost::shared_ptr<SMATModel> load_SMAT_Model(const char *filename, int npoints, int size = 15);
boost::shared_ptr<SMATModel> load_model(boost::shared_ptr<CvFileStorage> fs, 
					CvFileNode* root_node,
					unsigned int depth, 
					int npoints, int size=15);

boost::shared_ptr<SMATModel> load_SMAT_Model(const char *filename);
boost::shared_ptr<Shape> load_shape(boost::shared_ptr<CvFileStorage> fs, CvFileNode* root_node);
boost::shared_ptr<SMATModel> load_model(boost::shared_ptr<CvFileStorage> fs, 
					CvFileNode* root_node,
					unsigned int depth,
					const char *filename = NULL);//opencv has no function to recover the filename of a CvFileStorage... grrr
int write_current_shape(boost::shared_ptr<SMATModel>& model, 
			boost::shared_ptr<CvFileStorage> fs,
			float scale = 0, bool centre = false);
int init_model_shapes(boost::shared_ptr<SMATModel>& smat,
		      CvFileStorage* fs,
		      CvSeqReader& reader, const CvSeq* seq);
#endif
